/*
Copyright (c) 2011, Stephane Albert
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the organization nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL STEPHANE ALBERT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "debridFetcher.h"

std::map<std::string, std::string> debridFetcher::htmlChars;

debridFetcher::debridFetcher() {
    if (htmlChars.empty())
        initHtmlChars();
    logLevel = CALL_LOG;
    maxRetries = 5;
    mustExit = false;
    fileList = NULL;
    curlHandle = NULL;
    initCURL();
}

debridFetcher::~debridFetcher() {
    unloadCURL();
    if (fileList != NULL)
        delete fileList;
}

bool debridFetcher::initCURL() {
    unloadCURL();
    curlHandle = curl_easy_init();
    if (curlHandle != NULL)
        return true;
    return false;
}

bool debridFetcher::loadCURLSettings() {
    if (curlHandle != NULL) {
        curl_easy_reset(curlHandle);
        curl_easy_setopt(curlHandle, CURLOPT_ENCODING, "");         //Set default encoding to accept compressed pages (mandatory for filecrop)
        curl_easy_setopt(curlHandle, CURLOPT_COOKIEFILE, "");       //Set auto cookies
        return true;
    }
    return false;
}

void debridFetcher::unloadCURL() {
    if (curlHandle != NULL) {
        curl_easy_cleanup(curlHandle);
        curlHandle = NULL;
    }
}

void debridFetcher::setMaxRetries(int newMaxRetries) {
    if (newMaxRetries < 100)
        maxRetries = newMaxRetries;
    else
        maxRetries = 99;
}

int debridFetcher::getMaxRetries() const {
    return maxRetries;
}

//FIXME need a better error handling
void debridFetcher::setLogFile(const std::string& newLogFile) {
    if (logFile.is_open())
        logFile.close();
    logFile.open(newLogFile.c_str(), std::ios_base::out|std::ios_base::app);
    if (!logFile.good())
        logFile.close();
}

void debridFetcher::setFailedLogFile(const std::string& newFailedLogFile) {
    if (logFailedFile.is_open())
        logFailedFile.close();
    logFailedFile.open(newFailedLogFile.c_str(), std::ios_base::out|std::ios_base::app);
    if (!logFailedFile.good())
        logFailedFile.close();
}

void debridFetcher::setLogLevel(int newLogLevel) {
    if (logLevel > FALL_LOG)
        logLevel = FALL_LOG;
    else if (logLevel < 0)
        logLevel = NO_LOG;
    else
        logLevel = newLogLevel;
}

void debridFetcher::logMessage(const std::string& msg, int flags) {
    if (logLevel) {
        int logMSB;
        std::ostream *curStream = NULL;
        //Get logLevel MSB to skip unused log options
        for (logMSB=FMSG_LOG; !(logMSB & logLevel); logMSB >>= 1);
        for (int curFlag=logMSB; curFlag != LFFAIL_LOG; curFlag >>= 1) {
            if (logLevel & curFlag) {
                if (flags & 8) {    //0b1000 = Errors
                    switch (curFlag) {
                        case CFAIL_LOG:
                            curStream = &std::cerr;
                            break;
                        case FFAIL_LOG:
                            curStream = &logFile;
                            break;
                        default:
                            continue;
                    }
                }
                else {              //Standard output
                    switch (curFlag) {
                        case CMSG_LOG:
                            curStream = &std::cout;
                            break;
                        case FMSG_LOG:
                            curStream = &logFile;
                            break;
                        default:
                            continue;
                    }
                }
                if (flags == DEBRID) {
                    *curStream << _("Debrid: ");
                }
                else if (flags == DOWNLOAD) {
                    *curStream << _("Downloading: ");
                }
                else if (flags == DEBRID_ERR) {
                    *curStream << _("Error debrid: ");
                }
                else if (flags == DOWNLOAD_ERR) {
                    *curStream << _("Error downloading: ");
                }
                else if (flags == MISC_ERR) {
                    *curStream << _("Error: ");
                }
                *curStream << msg << std::endl;
                curStream->flush();
            }
        }
    }
}

void debridFetcher::logMessage(const std::string& msg, const std::string& linkURL, int flags) {
    if (logLevel)
        if (flags & 8)
            if (logLevel & LFFAIL_LOG)
                logFailedFile << linkURL << std::endl;
    std::string newMsg = linkURL + ", " + msg;
    logMessage(newMsg, flags);
}

void debridFetcher::setMonitorDir(const std::string& newMonitorDir) {
    if (!newMonitorDir.empty()) {
        monitorDirectory = newMonitorDir;
        if (newMonitorDir[newMonitorDir.size()-1] != DIR_SEP)
            monitorDirectory.push_back(DIR_SEP);
    }
}

std::string debridFetcher::getMonitorDir() const {
    return monitorDirectory;
}

void debridFetcher::setWorkingDir(const std::string& newWorkingDir) {
    if (!newWorkingDir.empty()) {
        workingDirectory = newWorkingDir;
        if (newWorkingDir[newWorkingDir.size()-1] != DIR_SEP)
            workingDirectory.push_back(DIR_SEP);
    }
}

std::string debridFetcher::getWorkingDir() const {
    return workingDirectory;
}

void debridFetcher::setDoneDir(const std::string& newDoneDir) {
    if (!newDoneDir.empty()) {
        doneDirectory = newDoneDir;
        if (newDoneDir[newDoneDir.size()-1] != DIR_SEP)
            doneDirectory.push_back(DIR_SEP);
    }
}

std::string debridFetcher::getDoneDir() const {
    return doneDirectory;
}

size_t debridFetcher::dataToStr(void *rawBuffer, size_t size, size_t nmemb, void *strPtr) {
    if (rawBuffer == NULL)
        return 0;
    std::string *outputString = (std::string*)strPtr;
    size_t initLen = outputString->length();
    outputString->append((char*)rawBuffer, nmemb);
    return outputString->length() - initLen;
}

size_t debridFetcher::dataToFile(void *rawBuffer, size_t size, size_t nmemb, void *filePtr) {
    if (rawBuffer == NULL)
        return 0;
    std::ofstream *outputFile = (std::ofstream*)filePtr;
    if (!outputFile->good())
        return 0;
    std::streampos initPos = outputFile->tellp();
    outputFile->write((const char*)rawBuffer, nmemb);
    return outputFile->tellp() - initPos;
}

int debridFetcher::seekFile(void *instream, curl_off_t offset, int origin) {
    if (instream == NULL)
        return CURL_SEEKFUNC_FAIL;
    std::ofstream *outputFile = (std::ofstream*)instream;
    if (!outputFile->good())
        return CURL_SEEKFUNC_FAIL;
    switch (origin) {
        case SEEK_SET: outputFile->seekp(offset, std::ios_base::beg);
            break;
        case SEEK_CUR: outputFile->seekp(offset);
            break;
        case SEEK_END: outputFile->seekp(offset, std::ios_base::end);
            break;
        default:
            return CURL_SEEKFUNC_FAIL;
    }
    return CURL_SEEKFUNC_OK;
}

std::string debridFetcher::postData(const std::string& URL, const std::string& postData) {
    if (curlHandle != NULL) {
        std::string outputData;
        loadCURLSettings();
        curl_easy_setopt(curlHandle, CURLOPT_URL, URL.c_str());
        curl_easy_setopt(curlHandle, CURLOPT_POSTFIELDS, postData.c_str());
        curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, dataToStr);
        curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, &outputData);
        CURLcode retCode = curl_easy_perform(curlHandle);
        if (retCode != CURLE_OK)
            return "";
        return outputData;
    }
    return "";
}

std::string debridFetcher::getData(const std::string& URL) {
    if (curlHandle != NULL) {
        std::string outputData;
        loadCURLSettings();
        curl_easy_setopt(curlHandle, CURLOPT_URL, URL.c_str());
        curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, dataToStr);
        curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, &outputData);
        CURLcode retCode = curl_easy_perform(curlHandle);
        if (retCode != CURLE_OK)
            return "";
        return outputData;
    }
    return "";
}

bool debridFetcher::authClient(const std::string& username, const std::string& password) {
    if (curlHandle != NULL) {
        std::string URL = "http://www.alldebrid.com/login/";
        std::string postStr = "pseudo="+username+"&password="+password;
        std::string retPage = postData(URL, postStr);
        if (retPage.length() == 0)
            return false;
        size_t isFound = retPage.find("<div id='login' class='green'>");
        if (isFound != std::string::npos)
            return true;
    }
    return false;
}

std::string debridFetcher::decodeLink(const std::string& linkURL) {
    if (fileList == NULL)
        return linkURL;
    std::string decodedURL;
    if (linkURL.find("wupload.com/") != std::string::npos) { //Sanitize wupload links
        decodedURL = linkURL;
        size_t remPos = decodedURL.find("/w867/");
        if (remPos != std::string::npos)
            decodedURL.erase(remPos, 5);
        return decodedURL;
    }
    else if (linkURL.find("filesonic.") != std::string::npos) { //Sanitize filesonic links
        decodedURL = linkURL;
        size_t remPos = decodedURL.find("/file/r");
        if (remPos != std::string::npos)
            decodedURL.erase(remPos+5, 11);
        return linkURL;
    }
    else if (linkURL.find("filecrop.com/") != std::string::npos) { //Decode filecrop links
        std::string linkPage = getData(linkURL);
        //TODO find a way to bypass captcha
        size_t linkPos = linkPage.find("/captcha.php?id");
        if (linkPos != std::string::npos) {    //Captcha requested
            linkPage.insert(linkPos, "http://www.filecrop.com");
            std::string access = doneDirectory + "access.html";
            std::ofstream accessFile;
            accessFile.open(access.c_str());
            if (accessFile.is_open()) {
                accessFile << linkPage;
                std::string errMSg = _("unable to decode link, captcha is required check page in: ");
                errMSg += access;
                errMSg += _(".\nThen restart the download.");
                logMessage(errMSg, linkURL, MISC_ERR);
            }
            return linkURL;
        }
        linkPos = linkPage.find("<a class=\"linkmaincolor\"");
        if (linkPos != std::string::npos) {     //Single link
            linkPos += 58;
            size_t linkEndPos = linkPage.find('"', linkPos);
            if (linkEndPos != std::string::npos) {
                decodedURL = linkPage.substr(linkPos, linkEndPos-linkPos);
            }
        }
        else if ((linkPos = linkPage.find("<table class=clear>")) != std::string::npos) {   //Multiple links
            std::string curLink = "http://www.filecrop.com";
            bool firstPage = true;
            do {
                if (!firstPage) {
                    --linkPos;
                    size_t begLinkPos = linkPage.rfind('"', linkPos);
                    ++begLinkPos;
                    std::string newPageURL = "http://www.filecrop.com/"+linkPage.substr(begLinkPos, linkPos-begLinkPos+1);
                    linkPage = getData(newPageURL);
                    linkPos = linkPage.find("<table class=clear>");
                }
                else
                    firstPage = false;
                do {
                    linkPos = linkPage.find("\" href=\"", linkPos);
                    linkPos += 8;
                    curLink.resize(23);
                    curLink += linkPage.substr(linkPos, linkPage.find('"', linkPos)-linkPos);
                    if (firstPage)
                        decodedURL = curLink;
                    else {
                        //Set the put pointer to the last line after the last read
                        //fileList->seekp(fileList->tellg());
                        fileList->seekp(0, std::ios_base::end);
                        *fileList << curLink;
                    }
                    linkPos = linkPage.find("<table class=clear>", linkPos);
                } while (linkPos != std::string::npos);
            } while ((linkPos = linkPage.find("\">Next</a></td>")) != std::string::npos);
        }
        else
            return linkURL;
    }
    else if (linkURL.find("filestube.com/") != std::string::npos) { //Decode filestube links
        std::string linkPage = getData(linkURL);
        size_t linkPos = linkPage.find("id=\"copy_paste_links\"");
        if (linkPos != std::string::npos) {
            linkPos = linkPage.find("http://", linkPos);
            size_t linkEndPos = linkPage.find("http://", linkPos+1);
            decodedURL = linkPage.substr(linkPos, linkEndPos-linkPos);
            for (int i=decodedURL.length()-1; decodedURL[i] == '\r' || decodedURL[i] == '\n' || decodedURL[i] == ' '; --i) {    //Sanitize URL
                decodedURL.erase(i, decodedURL.length()-i);
            }
            linkPos = linkEndPos;
            linkEndPos = linkPage.find("</pre> </div><script", linkPos);
            if (linkEndPos != std::string::npos) {
                //FIXME Shift fileList to keep same order
                //fileList->seekp(fileList->tellg());
                fileList->seekp(0, std::ios_base::end);
                *fileList << linkPage.substr(linkPos, linkEndPos-linkPos);
            }
            else
                return decodedURL;
        }
        else
            return linkURL;
    }
    else if (linkURL.find("debridor.com/") != std::string::npos) {  //Decode debridor links
        decodedURL = linkURL;
        if (linkURL[7] == 'w')
            decodedURL.erase(7, 4);
        std::string linkPage = getData(decodedURL);
        size_t linkPos = linkPage.find("<input name=\"get\"");
        if (linkPos != std::string::npos) {
            linkPos += 39;
            size_t linkEndPos = linkPage.find('"', linkPos);
            if (linkEndPos != std::string::npos) {
                std::string postStr = "get=";
                postStr.append(linkPage.substr(linkPos, linkEndPos-linkPos));
                postData("http://debridor.com/dl.php", postStr.c_str());
                char *newURL;
                if (curl_easy_getinfo(curlHandle, CURLINFO_REDIRECT_URL, &newURL) == CURLE_OK)
                    if (newURL == NULL)
                        return decodedURL;
                    decodedURL = newURL;
            }
        }
        else
            return decodedURL;
    }
    else if (linkURL.find("upsafe.org/") != std::string::npos) { //Decode upsafe links
        decodedURL = linkURL;
        decodedURL.replace(linkURL.find("upsafe.org/"), 10, "debridor.com");
    }
    else
        return linkURL;

    decodedURL = decodeLink(decodedURL);
    return decodedURL;
}

std::string debridFetcher::urlEncode(const std::string& rawURL) {
    std::string encodedURL;
    if (curlHandle != NULL) {
        char *escapedURL;
        escapedURL = curl_easy_escape(curlHandle, rawURL.c_str(), rawURL.length());
        if (escapedURL == NULL)
            return encodedURL;
        encodedURL = escapedURL;
        curl_free(escapedURL);
    }
    return encodedURL;
}

std::string debridFetcher::urlDecode(const std::string& rawURL) {
    std::string decodedURL;
    if (curlHandle != NULL) {
        char *unescapedURL;
        unescapedURL = curl_easy_unescape(curlHandle, rawURL.c_str(), rawURL.length(), NULL);
        if (unescapedURL == NULL)
            return decodedURL;
        decodedURL = unescapedURL;
        curl_free(unescapedURL);
    }
    return decodedURL;
}

std::string debridFetcher::debridLink(const std::string &linkURL) {
    if (curlHandle != NULL) {
        logMessage(linkURL, DEBRID);
        //Post AJAX Like
        std::string ajaxLinkURL = "http://www.alldebrid.com/service.php?link=";
        ajaxLinkURL.append(linkURL);
        std::string retPage = getData(ajaxLinkURL);
        if (retPage.length() == 0)
            return _("Unable to fetch AJAX request.");
        //Error handling
        if (retPage[0] != '0') {
            if (retPage[0] == '1')
                return "Invalid link";
            if (retPage[0] == '2')
                return "Link is dead";
            if (retPage[0] == '3')
                return "Host is under maintenance.";
            if (retPage[0] == '{') {    //Json bug
                size_t errorMsgPos = retPage.rfind(':');
                errorMsgPos += 2;
                size_t errorMsgLen = retPage.length();
                errorMsgLen -= errorMsgPos + 2;     //+2 removes "\"}"
                return retPage.substr(errorMsgPos, errorMsgLen);
            }
            return _("Unknown Error");
        }
        size_t begPos = retPage.find("<a href=");
        if (begPos != std::string::npos) {
            begPos += 9;
            return retPage.substr(begPos, (retPage.find('\'', begPos) - begPos));
        }
    }
    return _("CURL not init.");
}

int debridFetcher::getFile(const std::string& fileURL) {
    if (curlHandle != NULL) {
        logMessage(fileURL, DOWNLOAD);
        std::string filePath;
        size_t begPos = fileURL.rfind('/');
        if (begPos == std::string::npos)
            return URL_ERR;
        ++begPos;
        filePath = workingDirectory;
        filePath.append(htmlDecode(fileURL.substr(begPos)));
        std::ofstream outFile(filePath.c_str(), std::ios_base::binary|std::ios_base::out);
        if (!outFile.is_open()) {
            return SYS_ERR;
        }
        loadCURLSettings();
        curl_easy_setopt(curlHandle, CURLOPT_URL, fileURL.c_str());
        curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, dataToFile);
        curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, &outFile);
        curl_easy_setopt(curlHandle, CURLOPT_SEEKFUNCTION, seekFile);
        curl_easy_setopt(curlHandle, CURLOPT_SEEKDATA, &outFile);
        if (logLevel & CMSG_LOG)
            curl_easy_setopt(curlHandle, CURLOPT_NOPROGRESS, 0);
        //Stalled transfert detection
        curl_easy_setopt(curlHandle, CURLOPT_LOW_SPEED_LIMIT, MIN_SPEED);    //Stalled Transfert detection 1Kb/s
        //FIXME Remove debug
        #ifdef DEBUG
            curl_easy_setopt(curlHandle, CURLOPT_LOW_SPEED_TIME, 5);
        #else
        curl_easy_setopt(curlHandle, CURLOPT_LOW_SPEED_TIME, MIN_SPEED_TIMEOUT);       //< 1 Kbps for 60 seconds
        #endif

        int funcRetCode = CONN_ERR;
        CURLcode retCode;
        std::streamoff resumePos=0;
        for (int retry=maxRetries; retry > 0; --retry) {
            retCode = curl_easy_perform(curlHandle);
            /*
             * Stalled transfert check
             */
            if (retCode == CURLE_OPERATION_TIMEDOUT) {
                resumePos = outFile.tellp();
                if (resumePos > 0) {    //Definitely stalled
                    logMessage(_("Transfert stalled, trying to resume."), MISC_ERR);
                    curl_easy_setopt(curlHandle, CURLOPT_RESUME_FROM, resumePos);
                    continue;
                }
            }
            /*
             * An error occurred
             */
            else if (retCode != CURLE_OK) {
                //Size checking
                double contentLength;
                if (curl_easy_getinfo(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength) == CURLE_OK) {
                    if (contentLength > 0) {        //If contentLength == -1 skip
                        resumePos = outFile.tellp();
                        if (resumePos != contentLength) {   //Try to resume
                            logMessage(_("Transfert interrupted, trying to resume."), MISC_ERR);
                            curl_easy_setopt(curlHandle, CURLOPT_RESUME_FROM, resumePos);
                            continue;
                        }
                    }
                }
                resumePos = 0;
            }
            /*
             * No error
             */
            else {
                //Size checking
                double contentLength;
                funcRetCode = NO_ERR;
                if (curl_easy_getinfo(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLength) == CURLE_OK) {
                    if (contentLength > 0) {        //If contentLength == -1 skip
                        if (outFile.tellp()-resumePos != contentLength) {
                            std::string errorMessage = _("Size mismatch, expected: ");
                            errorMessage.append(doubleToString(contentLength));
                            errorMessage.append(_(" found: "));
                            errorMessage.append(doubleToString(outFile.tellp()-resumePos));
                            logMessage(errorMessage, MISC_ERR);
                            curl_easy_reset(curlHandle);
                            funcRetCode = SIZE_ERR;
                        }
                        else {		//If size match
							if (contentLength < 1024) {		//If size < 1Kb might be an error message
								outFile.close();
								std::ifstream inFile;
								inFile.open(filePath.c_str(), std::ios_base::binary);
								if (!inFile.good()) {
									funcRetCode = SIZE_ERR;
									break;
								}
								char buffer[90];
								inFile.read(buffer, 89);
								buffer[inFile.gcount()] = '\0';
								std::string errorMsg = "An error is happend to generate premium link, please contact administrator with this info";
								if (errorMsg.compare(0, 89, buffer) == 0) {		//Definitly an error message
								    //All hopes are lost we need to debrid it again !
									logMessage(_("Server returned an error message, retrying debrid."), MISC_ERR);
									funcRetCode = HOST_ERR;
								}
							}
						}
                    }
                }
                break;
            }
            logMessage(_("Error during transfert, retrying."), MISC_ERR);
        }
        return funcRetCode;
    }
    return CONN_ERR;
}

void debridFetcher::dlEachFile() {
    logMessage(_("Downloading list."));
    std::string curFile;
    std::string decodedURL;
    std::string dlLink;
    int retCode;
    bool debridDone = false;
    while (fileList->good()) {
        getline(*fileList, curFile);
        for (int i=curFile.length()-1; curFile[i] == '\r' || curFile[i] == '\n' || curFile[i] == ' '; --i) {    //Sanitize URL
            curFile.erase(i, curFile.length()-i);
        }
        if (curFile.length() < 3) { //Possible new lines, dirty
            continue;
        }
        if (curFile.find("alldebrid.") != std::string::npos)
            debridDone = true;
        else
            decodedURL = decodeLink(curFile);
        for (int retry=maxRetries; retry > 0; --retry) {
            if (!debridDone) {
                dlLink = debridLink(decodedURL);
                if (dlLink.compare(0, 7, "http://")) {     //If the URL doesn't begin with http:// then it's an error
                    logMessage(dlLink, decodedURL, DEBRID_ERR);
                    break;
                }
            }
            else    //File is already ready -> direct download
                dlLink = curFile;

            retCode = getFile(dlLink);
            std::string filePath = workingDirectory;
            size_t begPos = dlLink.rfind('/');
            ++begPos;
            filePath.append(htmlDecode(dlLink.substr(begPos)));

            if (retCode != NO_ERR) {
                std::string errorMsg;
                switch (retCode) {
                    case URL_ERR:
                        errorMsg = _("incorrect URL.");
                    break;
                    case CONN_ERR:
                        errorMsg = _("connection problem.");
                    break;
                    case SYS_ERR:
                        errorMsg = _("filesystem problem, unable to create temporary file.");
                    break;
                    case HOST_ERR: {
                        if (debridDone)
                            errorMsg = _("server error.");
                        else {
                            errorMsg = _("server side error, trying to debrid the link again.");
                            logMessage(errorMsg, DOWNLOAD_ERR);
                            continue;
                        }
                    }
                    break;
                    case SIZE_ERR:
                        errorMsg = _("size error.");
                    break;
                }
                remove(filePath.c_str());
                logMessage(errorMsg, curFile, DOWNLOAD_ERR);
                break;
            }
            else {
                std::string newFilePath = doneDirectory;
                newFilePath.append(htmlDecode(dlLink.substr(begPos)));
                if (rename(filePath.c_str(), newFilePath.c_str()) != 0) {
                    //Rename/Moving failed, file must be in a different FS: copying
                    std::ifstream oldFile;
                    std::ofstream newFile;
                    oldFile.open(filePath.c_str(), std::ifstream::binary);
                    if (!oldFile.is_open())
                        break;
                    newFile.open(newFilePath.c_str(), std::ifstream::binary);
                    if (!newFile.is_open())
                        break;
                    newFile << oldFile.rdbuf();
                    oldFile.close();
                    remove(filePath.c_str());
                }
                break;
            }
        }
    }
    logMessage(_("Finished downloading list."));
}

void debridFetcher::processDownloadList(const std::string& downloadList) {
    if (fileList != NULL)   //Shouldn't happen
        delete fileList;
    fileList = getFileContent(downloadList);
    if (fileList != NULL) {
        if (remove(downloadList.c_str()) != 0) {
            logMessage(_("Unable to remove file, stopping thread."), MISC_ERR);
            mustExit = true;
        }
        else
            dlEachFile();
        delete fileList;
        fileList = NULL;
    }
    /*else    //FIXME could produce a deadlock
        logMessage(_("Unable to open a list from watchdir."), MISC_ERR);*/
}

std::stringstream *debridFetcher::getFileContent(const std::string& filePath) {
    if (filePath.empty())
        return NULL;
    std::ifstream inputFile(filePath.c_str());
    if (!inputFile.good())
        return NULL;
    std::stringstream *fileContent = new std::stringstream;
    *fileContent << inputFile.rdbuf();
    inputFile.close();
    return fileContent;
}

//FIXME modify curl call to cancel transferts for mustexit
void debridFetcher::exit(bool forceExit) {
    mustExit = true;
    /*if (forceExit)
        curl_easy curlHandle*/
}

std::string debridFetcher::htmlEncode(const std::string& rawString) {
    std::string result = rawString;
    std::map<std::string, std::string>::iterator curPos = htmlChars.begin();
    std::map<std::string, std::string>::iterator endPos = htmlChars.end();
    size_t lastFound;
    while (curPos != endPos) {
        lastFound = result.find(curPos->first);
        while (lastFound != std::string::npos) {
            result.replace(lastFound, curPos->first.size(), curPos->second);
            lastFound += curPos->second.size();
            lastFound = result.find(curPos->first, lastFound);
        }
        ++curPos;
    }
    return result;
}

std::string debridFetcher::htmlDecode(const std::string& rawString) {
    std::string result = rawString;
    std::map<std::string, std::string>::iterator curPos = htmlChars.begin();
    std::map<std::string, std::string>::iterator endPos = htmlChars.end();
    size_t lastFound;
    while (curPos != endPos) {
        lastFound = result.find(curPos->second);
        while (lastFound != std::string::npos) {
            result.replace(lastFound, curPos->second.size(), curPos->first);
            lastFound += curPos->first.size();
            lastFound = result.find(curPos->second, lastFound);
        }
        ++curPos;
    }
    return result;
}

void debridFetcher::initHtmlChars() {
    htmlChars.clear();
    htmlChars.insert(std::pair<std::string, std::string>("&", "&amp;"));
    htmlChars.insert(std::pair<std::string, std::string>("<", "&lt;"));
    htmlChars.insert(std::pair<std::string, std::string>(">", "&gt;"));
    htmlChars.insert(std::pair<std::string, std::string>("\"", "&quot;"));
    htmlChars.insert(std::pair<std::string, std::string>("'", "&#039;"));
    htmlChars.insert(std::pair<std::string, std::string>(" ", "&nbsp;"));
    htmlChars.insert(std::pair<std::string, std::string>("£", "&pound;"));
    htmlChars.insert(std::pair<std::string, std::string>("à", "&agrave;"));
    htmlChars.insert(std::pair<std::string, std::string>("ç", "&ccedil;"));
    htmlChars.insert(std::pair<std::string, std::string>("è", "&egrave;"));
    htmlChars.insert(std::pair<std::string, std::string>("é", "&eacute;"));
    htmlChars.insert(std::pair<std::string, std::string>("ê", "&ecirc;"));
    htmlChars.insert(std::pair<std::string, std::string>("ù", "&ugrave;"));
    htmlChars.insert(std::pair<std::string, std::string>("©", "&copy;"));
    htmlChars.insert(std::pair<std::string, std::string>("Ã", "&Atilde;"));
}
